﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;

namespace SampleBrowser.Util
{
    /// <summary>
    /// Provides access to the resources in a stream.
    /// </summary>
    public class ResourceReader
    {
        /// <summary>
        /// Gets the names of the resources in given stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns></returns>
        public IEnumerable<string> GetResourceNames(Stream stream)
        {
            using (var reader = new BinaryReader(stream)) {
                uint signature = reader.ReadUInt32();
                if (signature != 0xbeefcace) {
                    yield break; // no resources
                }

                int headerVersion = reader.ReadInt32();
                if (headerVersion > 1) {
                    reader.BaseStream.Seek((long)reader.ReadInt32(), SeekOrigin.Current);
                }
                else {
                    reader.ReadInt32(); // skip

                    string resourceReaderType = reader.ReadString();
                    string resourceSetType = reader.ReadString();

                    if (!resourceReaderType.StartsWith("System.Resources.ResourceReader")) {
                        throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Resource reader type '{0}' is not supported.", resourceReaderType));
                    }
                }

                int runtimeVersion = reader.ReadInt32();
                if ((runtimeVersion != 2) && (runtimeVersion != 1)) {
                    throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Unknown runtime version \'{0}\'.", runtimeVersion));
                }

                int resourceCount = reader.ReadInt32();

                string[] resourceTypeNames = new string[reader.ReadInt32()];
                for (int i = 0; i < resourceTypeNames.Length; i++) {
                    resourceTypeNames[i] = reader.ReadString();
                }

                if ((reader.BaseStream.Position % 8) != 0) {
                    reader.ReadBytes((int)(8 - (reader.BaseStream.Position % 8)));
                }

                int[] resourceNameHashValues = new int[resourceCount];
                for (int i = 0; i < resourceNameHashValues.Length; i++) {
                    resourceNameHashValues[i] = reader.ReadInt32();
                }

                int[] resourceNamePositions = new int[resourceCount];
                for (int i = 0; i < resourceNamePositions.Length; i++) {
                    resourceNamePositions[i] = reader.ReadInt32();
                }

                long resourceValueSectionOffset = reader.ReadInt32();
                long resourceNameSectionOffset = reader.BaseStream.Position;

                for (int i = 0; i < resourceCount; i++) {
                    reader.BaseStream.Position = resourceNameSectionOffset + resourceNamePositions[i];

                    int count = this.Read7BitEncodedInt(reader);
                    byte[] bytes = reader.ReadBytes(count);
                    yield return Encoding.Unicode.GetString(bytes, 0, count);
                }
            }
        }

        private int Read7BitEncodedInt(BinaryReader reader)
        {
            int value = 0;
            int shift = 0;
            byte b;

            do {
                b = reader.ReadByte();
                value |= (b & 0x7f) << shift;
                shift += 7;
            }
            while ((b & 0x80) != 0);

            return value;
        }
    }
}
